#ifndef STX_PLATFORM_POSIX_THREAD_READ_WRITE_LOCK_HPP
#define STX_PLATFORM_POSIX_THREAD_READ_WRITE_LOCK_HPP

#include <stx/platform/posix/thread/common.hpp>
#include <stx/platform/posix/thread/read_write_lock_attributes.hpp>

namespace stx {

namespace posix {

class read_write_lock: noncopyable
{
public:
    
    typedef read_write_lock this_type;
    
    read_write_lock()
    {
        int ret = pthread_rwlock_init(&handle_, NULL);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_rwlock_init");
        }
    }
    
    read_write_lock(const read_write_lock_attributes& attr)
    {
        int ret = pthread_rwlock_init(&handle_, attr.handle());
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_rwlock_init");
        }
    }
    
    ~read_write_lock()
    {
        int ret = pthread_rwlock_destroy(&handle_);
        if (ret) {
            STX_DESTRUCTOR_SYSTEM_ERROR_VAL(ret, "pthread_rwlock_destroy");
        }
    }
    
    void read_lock()
    {
        int ret = pthread_rwlock_rdlock(&handle_);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_rwlock_rdlock");
        }
    }
    
    bool try_read_lock()
    {
        int ret = pthread_rwlock_tryrdlock(&handle_);
        if (ret == EBUSY) {
            return false;
        } else  {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_rwlock_tryrdlock");
        }
        return true;
    }
    
    bool timed_read_lock(const struct timespec& timeout)
    {
        int ret = pthread_rwlock_timedrdlock(&handle_, &timeout);
        if (ret == ETIMEDOUT) {
            return false;
        } else {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_rwlock_timedrdlock");
        }
        return true;
    }
    
    void write_lock()
    {
        int ret = pthread_rwlock_wrlock(&handle_);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_rwlock_wrlock");
        }
    }
    
    bool try_write_lock()
    {
        int ret = pthread_rwlock_trywrlock(&handle_);
        if (ret == EBUSY) {
            return false;
        } else  {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_rwlock_trywrlock");
        }
        return true;
    }
    
    bool timed_write_lock(const struct timespec& timeout)
    {
        int ret = pthread_rwlock_timedwrlock(&handle_, &timeout);
        if (ret == ETIMEDOUT) {
            return false;
        } else {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_rwlock_timedwrlock");
        }
        return true;
    }
    
    void lock()
    {
        write_lock();
    }
    
    void unlock()
    {
        int ret = pthread_rwlock_unlock(&handle_);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_rwlock_unlock");
        }
    }
    
    typedef pthread_rwlock_t handle_type;
    
    handle_type* handle()
    {
        return &handle_;
    }
    
    const handle_type* handle() const
    {
        return &handle_;
    }
    
private:
    
    handle_type handle_;
};

} // namespace posix

} // namespace stx

#endif // STX_PLATFORM_POSIX_THREAD_READ_WRITE_LOCK_HPP
